/*****************************************************************************/
/*                                                                           */
/*                                  preproc.c                                */
/*                                                                           */
/*                              cc65 preprocessor                            */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 1998-2010, Ullrich von Bassewitz                                      */
/*                Roemerstrasse 52                                           */
/*                D-70794 Filderstadt                                        */
/* EMail:         uz@cc65.org                                                */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

/* common */
#include "chartype.h"
#include "check.h"
#include "inline.h"
#include "print.h"
#include "xmalloc.h"

/* cc65 */
#include "codegen.h"
#include "error.h"
#include "expr.h"
#include "global.h"
#include "ident.h"
#include "incpath.h"
#include "input.h"
#include "lineinfo.h"
#include "macrotab.h"
#include "preproc.h"
#include "scanner.h"
#include "standard.h"

/*****************************************************************************/
/*                                   Data                                    */
/*****************************************************************************/

/* Set when the preprocessor calls expr() recursively */
unsigned char Preprocessing = 0;

/* Management data for #if */
#define MAX_IFS 64
#define IFCOND_NONE 0x00U
#define IFCOND_SKIP 0x01U
#define IFCOND_ELSE 0x02U
#define IFCOND_NEEDTERM 0x04U
static unsigned char IfStack[MAX_IFS];
static int IfIndex = -1;

/* Buffer for macro expansion */
static StrBuf *MLine;

/* Structure used when expanding macros */
typedef struct MacroExp MacroExp;
struct MacroExp {
  Collection ActualArgs; /* Actual arguments */
  StrBuf Replacement;    /* Replacement with arguments substituted */
  Macro *M;              /* The macro we're handling */
};

/*****************************************************************************/
/*                                 Forwards                                  */
/*****************************************************************************/

static unsigned Pass1(StrBuf *Source, StrBuf *Target);
/* Preprocessor pass 1. Remove whitespace. Handle old and new style comments
** and the "defined" operator.
*/

static void MacroReplacement(StrBuf *Source, StrBuf *Target);
/* Perform macro replacement. */

/*****************************************************************************/
/*                   Low level preprocessor token handling                   */
/*****************************************************************************/

/* Types of preprocessor tokens */
typedef enum {
  PP_ILLEGAL = -1,
  PP_DEFINE,
  PP_ELIF,
  PP_ELSE,
  PP_ENDIF,
  PP_ERROR,
  PP_IF,
  PP_IFDEF,
  PP_IFNDEF,
  PP_INCLUDE,
  PP_LINE,
  PP_PRAGMA,
  PP_UNDEF,
  PP_WARNING,
} pptoken_t;

/* Preprocessor keyword to token mapping table */
static const struct PPToken {
  const char *Key; /* Keyword */
  pptoken_t Tok;   /* Token */
} PPTokens[] = {
    {"define", PP_DEFINE}, {"elif", PP_ELIF},   {"else", PP_ELSE},       {"endif", PP_ENDIF},     {"error", PP_ERROR},
    {"if", PP_IF},         {"ifdef", PP_IFDEF}, {"ifndef", PP_IFNDEF},   {"include", PP_INCLUDE}, {"line", PP_LINE},
    {"pragma", PP_PRAGMA}, {"undef", PP_UNDEF}, {"warning", PP_WARNING},
};

/* Number of preprocessor tokens */
#define PPTOKEN_COUNT (sizeof(PPTokens) / sizeof(PPTokens[0]))

static int CmpToken(const void *Key, const void *Elem)
/* Compare function for bsearch */
{
  return strcmp((const char *)Key, ((const struct PPToken *)Elem)->Key);
}

static pptoken_t FindPPToken(const char *Ident)
/* Find a preprocessor token and return it. Return PP_ILLEGAL if the identifier
** is not a valid preprocessor token.
*/
{
  struct PPToken *P;
  P = bsearch(Ident, PPTokens, PPTOKEN_COUNT, sizeof(PPTokens[0]), CmpToken);
  return P ? P->Tok : PP_ILLEGAL;
}

/*****************************************************************************/
/*                              struct MacroExp                              */
/*****************************************************************************/

static MacroExp *InitMacroExp(MacroExp *E, Macro *M)
/* Initialize a MacroExp structure */
{
  InitCollection(&E->ActualArgs);
  SB_Init(&E->Replacement);
  E->M = M;
  return E;
}

static void DoneMacroExp(MacroExp *E)
/* Cleanup after use of a MacroExp structure */
{
  unsigned I;

  /* Delete the list with actual arguments */
  for (I = 0; I < CollCount(&E->ActualArgs); ++I) {
    FreeStrBuf(CollAtUnchecked(&E->ActualArgs, I));
  }
  DoneCollection(&E->ActualArgs);
  SB_Done(&E->Replacement);
}

static void ME_AppendActual(MacroExp *E, StrBuf *Arg)
/* Add a copy of Arg to the list of actual macro arguments.
** NOTE: This function will clear Arg!
*/
{
  /* Create a new string buffer */
  StrBuf *A = NewStrBuf();

  /* Move the contents of Arg to A */
  SB_Move(A, Arg);

  /* Add A to the actual arguments */
  CollAppend(&E->ActualArgs, A);
}

static StrBuf *ME_GetActual(MacroExp *E, unsigned Index)
/* Return an actual macro argument with the given index */
{
  return CollAt(&E->ActualArgs, Index);
}

static int ME_ArgIsVariadic(const MacroExp *E)
/* Return true if the next actual argument we will add is a variadic one */
{
  return (E->M->Variadic && E->M->ArgCount == (int)CollCount(&E->ActualArgs) + 1);
}

/*****************************************************************************/
/*                                   Code                                    */
/*****************************************************************************/

static void Stringize(StrBuf *Source, StrBuf *Target)
/* Stringize the given string: Add double quotes at start and end and preceed
** each occurance of " and \ by a backslash.
*/
{
  char C;

  /* Add a starting quote */
  SB_AppendChar(Target, '\"');

  /* Replace any characters inside the string may not be part of a string
  ** unescaped.
  */
  while ((C = SB_Get(Source)) != '\0') {
    switch (C) {
    case '\"':
    case '\\':
      SB_AppendChar(Target, '\\');
    /* FALLTHROUGH */
    default:
      SB_AppendChar(Target, C);
      break;
    }
  }

  /* Add the closing quote */
  SB_AppendChar(Target, '\"');
}

static void OldStyleComment(void)
/* Remove an old style C comment from line. */
{
  /* Remember the current line number, so we can output better error
  ** messages if the comment is not terminated in the current file.
  */
  unsigned StartingLine = GetCurrentLine();

  /* Skip the start of comment chars */
  NextChar();
  NextChar();

  /* Skip the comment */
  while (CurC != '*' || NextC != '/') {
    if (CurC == '\0') {
      if (NextLine() == 0) {
        PPError("End-of-file reached in comment starting at line %u", StartingLine);
        return;
      }
    } else {
      if (CurC == '/' && NextC == '*') {
        PPWarning("'/*' found inside a comment");
      }
      NextChar();
    }
  }

  /* Skip the end of comment chars */
  NextChar();
  NextChar();
}

static void NewStyleComment(void)
/* Remove a new style C comment from line. */
{
  /* Beware: Because line continuation chars are handled when reading
  ** lines, we may only skip until the end of the source line, which
  ** may not be the same as the end of the input line. The end of the
  ** source line is denoted by a lf (\n) character.
  */
  do {
    NextChar();
  } while (CurC != '\n' && CurC != '\0');
  if (CurC == '\n') {
    NextChar();
  }
}

static int SkipWhitespace(int SkipLines)
/* Skip white space in the input stream. Do also skip newlines if SkipLines
** is true. Return zero if nothing was skipped, otherwise return a
** value != zero.
*/
{
  int Skipped = 0;
  while (1) {
    if (IsSpace(CurC)) {
      NextChar();
      Skipped = 1;
    } else if (CurC == '\0' && SkipLines) {
      /* End of line, read next */
      if (NextLine() != 0) {
        Skipped = 1;
      } else {
        /* End of input */
        break;
      }
    } else {
      /* No more white space */
      break;
    }
  }
  return Skipped;
}

static void CopyQuotedString(StrBuf *Target)
/* Copy a single or double quoted string from the input to Target. */
{
  /* Remember the quote character, copy it to the target buffer and skip it */
  char Quote = CurC;
  SB_AppendChar(Target, CurC);
  NextChar();

  /* Copy the characters inside the string */
  while (CurC != '\0' && CurC != Quote) {
    /* Keep an escaped char */
    if (CurC == '\\') {
      SB_AppendChar(Target, CurC);
      NextChar();
    }
    /* Copy the character */
    SB_AppendChar(Target, CurC);
    NextChar();
  }

  /* If we had a terminating quote, copy it */
  if (CurC != '\0') {
    SB_AppendChar(Target, CurC);
    NextChar();
  }
}

/*****************************************************************************/
/*                                Macro stuff                                */
/*****************************************************************************/

static int MacName(char *Ident)
/* Get a macro symbol name into Ident.  If we have an error, print a
** diagnostic message and clear the line.
*/
{
  if (IsSym(Ident) == 0) {
    PPError("Identifier expected");
    ClearLine();
    return 0;
  } else {
    return 1;
  }
}

static void ReadMacroArgs(MacroExp *E)
/* Identify the arguments to a macro call */
{
  unsigned Parens; /* Number of open parenthesis */
  StrBuf Arg = STATIC_STRBUF_INITIALIZER;

  /* Read the actual macro arguments */
  Parens = 0;
  while (1) {
    if (CurC == '(') {

      /* Nested parenthesis */
      SB_AppendChar(&Arg, CurC);
      NextChar();
      ++Parens;

    } else if (IsQuote(CurC)) {

      /* Quoted string - just copy */
      CopyQuotedString(&Arg);

    } else if (CurC == ',' || CurC == ')') {

      if (Parens) {
        /* Comma or right paren inside nested parenthesis */
        if (CurC == ')') {
          --Parens;
        }
        SB_AppendChar(&Arg, CurC);
        NextChar();
      } else if (CurC == ',' && ME_ArgIsVariadic(E)) {
        /* It's a comma, but we're inside a variadic macro argument, so
        ** just copy it and proceed.
        */
        SB_AppendChar(&Arg, CurC);
        NextChar();
      } else {
        /* End of actual argument. Remove whitespace from the end. */
        while (IsSpace(SB_LookAtLast(&Arg))) {
          SB_Drop(&Arg, 1);
        }

        /* If this is not the single empty argument for a macro with
        ** an empty argument list, remember it.
        */
        if (CurC != ')' || SB_NotEmpty(&Arg) || E->M->ArgCount > 0) {
          ME_AppendActual(E, &Arg);
        }

        /* Check for end of macro param list */
        if (CurC == ')') {
          NextChar();
          break;
        }

        /* Start the next param */
        NextChar();
        SB_Clear(&Arg);
      }
    } else if (SkipWhitespace(1)) {
      /* Squeeze runs of blanks within an arg */
      if (SB_NotEmpty(&Arg)) {
        SB_AppendChar(&Arg, ' ');
      }
    } else if (CurC == '/' && NextC == '*') {
      if (SB_NotEmpty(&Arg)) {
        SB_AppendChar(&Arg, ' ');
      }
      OldStyleComment();
    } else if (IS_Get(&Standard) >= STD_C99 && CurC == '/' && NextC == '/') {
      if (SB_NotEmpty(&Arg)) {
        SB_AppendChar(&Arg, ' ');
      }
      NewStyleComment();
    } else if (CurC == '\0') {
      /* End of input inside macro argument list */
      PPError("Unterminated argument list invoking macro '%s'", E->M->Name);

      ClearLine();
      break;
    } else {
      /* Just copy the character */
      SB_AppendChar(&Arg, CurC);
      NextChar();
    }
  }

  /* Deallocate string buf resources */
  SB_Done(&Arg);
}

static void MacroArgSubst(MacroExp *E)
/* Argument substitution according to ISO/IEC 9899:1999 (E), 6.10.3.1ff */
{
  ident Ident;
  int ArgIdx;
  StrBuf *OldSource;
  StrBuf *Arg;
  int HaveSpace;

  /* Remember the current input and switch to the macro replacement. */
  int OldIndex = SB_GetIndex(&E->M->Replacement);
  SB_Reset(&E->M->Replacement);
  OldSource = InitLine(&E->M->Replacement);

  /* Argument handling loop */
  while (CurC != '\0') {

    /* If we have an identifier, check if it's a macro */
    if (IsSym(Ident)) {

      /* Check if it's a macro argument */
      if ((ArgIdx = FindMacroArg(E->M, Ident)) >= 0) {

        /* A macro argument. Get the corresponding actual argument. */
        Arg = ME_GetActual(E, ArgIdx);

        /* Copy any following whitespace */
        HaveSpace = SkipWhitespace(0);

        /* If a ## operator follows, we have to insert the actual
        ** argument as is, otherwise it must be macro replaced.
        */
        if (CurC == '#' && NextC == '#') {

          /* ### Add placemarker if necessary */
          SB_Append(&E->Replacement, Arg);

        } else {

          /* Replace the formal argument by a macro replaced copy
          ** of the actual.
          */
          SB_Reset(Arg);
          MacroReplacement(Arg, &E->Replacement);

          /* If we skipped whitespace before, re-add it now */
          if (HaveSpace) {
            SB_AppendChar(&E->Replacement, ' ');
          }
        }

      } else {

        /* An identifier, keep it */
        SB_AppendStr(&E->Replacement, Ident);
      }

    } else if (CurC == '#' && NextC == '#') {

      /* ## operator. */
      NextChar();
      NextChar();
      SkipWhitespace(0);

      /* Since we need to concatenate the token sequences, remove
      ** any whitespace that was added to target, since it must come
      ** from the input.
      */
      while (IsSpace(SB_LookAtLast(&E->Replacement))) {
        SB_Drop(&E->Replacement, 1);
      }

      /* If the next token is an identifier which is a macro argument,
      ** replace it, otherwise do nothing.
      */
      if (IsSym(Ident)) {

        /* Check if it's a macro argument */
        if ((ArgIdx = FindMacroArg(E->M, Ident)) >= 0) {

          /* Get the corresponding actual argument and add it. */
          SB_Append(&E->Replacement, ME_GetActual(E, ArgIdx));

        } else {

          /* Just an ordinary identifier - add as is */
          SB_AppendStr(&E->Replacement, Ident);
        }
      }

    } else if (CurC == '#' && E->M->ArgCount >= 0) {

      /* A # operator within a macro expansion of a function like
      ** macro. Read the following identifier and check if it's a
      ** macro parameter.
      */
      NextChar();
      SkipWhitespace(0);
      if (!IsSym(Ident) || (ArgIdx = FindMacroArg(E->M, Ident)) < 0) {
        PPError("'#' is not followed by a macro parameter");
      } else {
        /* Make a valid string from Replacement */
        Arg = ME_GetActual(E, ArgIdx);
        SB_Reset(Arg);
        Stringize(Arg, &E->Replacement);
      }

    } else if (IsQuote(CurC)) {
      CopyQuotedString(&E->Replacement);
    } else {
      SB_AppendChar(&E->Replacement, CurC);
      NextChar();
    }
  }

#if 0
    /* Remove whitespace from the end of the line */
    while (IsSpace (SB_LookAtLast (&E->Replacement))) {
        SB_Drop (&E->Replacement, 1);
    }
#endif

  /* Switch back the input */
  InitLine(OldSource);
  SB_SetIndex(&E->M->Replacement, OldIndex);
}

static void MacroCall(StrBuf *Target, Macro *M)
/* Process a function like macro */
{
  MacroExp E;

  /* Eat the left paren */
  NextChar();

  /* Initialize our MacroExp structure */
  InitMacroExp(&E, M);

  /* Read the actual macro arguments */
  ReadMacroArgs(&E);

  /* Compare formal and actual argument count */
  if (CollCount(&E.ActualArgs) != (unsigned)M->ArgCount) {

    StrBuf Arg = STATIC_STRBUF_INITIALIZER;

    /* Argument count mismatch */
    PPError("Macro argument count mismatch");

    /* Be sure to make enough empty arguments available */
    while (CollCount(&E.ActualArgs) < (unsigned)M->ArgCount) {
      ME_AppendActual(&E, &Arg);
    }
  }

  /* Replace macro arguments handling the # and ## operators */
  MacroArgSubst(&E);

  /* Do macro replacement on the macro that already has the parameters
  ** substituted.
  */
  M->Expanding = 1;
  MacroReplacement(&E.Replacement, Target);
  M->Expanding = 0;

  /* Free memory allocated for the macro expansion structure */
  DoneMacroExp(&E);
}

static void ExpandMacro(StrBuf *Target, Macro *M)
/* Expand a macro into Target */
{
#if 0
    static unsigned V = 0;
    printf ("Expanding %s(%u)\n", M->Name, ++V);
#endif

  /* Check if this is a function like macro */
  if (M->ArgCount >= 0) {

    int Whitespace = SkipWhitespace(1);
    if (CurC != '(') {
      /* Function like macro but no parameter list */
      SB_AppendStr(Target, M->Name);
      if (Whitespace) {
        SB_AppendChar(Target, ' ');
      }
    } else {
      /* Function like macro */
      MacroCall(Target, M);
    }

  } else {

    MacroExp E;
    InitMacroExp(&E, M);

    /* Handle # and ## operators for object like macros */
    MacroArgSubst(&E);

    /* Do macro replacement on the macro that already has the parameters
    ** substituted.
    */
    M->Expanding = 1;
    MacroReplacement(&E.Replacement, Target);
    M->Expanding = 0;

    /* Free memory allocated for the macro expansion structure */
    DoneMacroExp(&E);
  }
#if 0
    printf ("Done with %s(%u)\n", M->Name, V--);
#endif
}

static void DefineMacro(void)
/* Handle a macro definition. */
{
  ident Ident;
  Macro *M;
  Macro *Existing;
  int C89;

  /* Read the macro name */
  SkipWhitespace(0);
  if (!MacName(Ident)) {
    return;
  }

  /* Remember if we're in C89 mode */
  C89 = (IS_Get(&Standard) == STD_C89);

  /* Get an existing macro definition with this name */
  Existing = FindMacro(Ident);

  /* Create a new macro definition */
  M = NewMacro(Ident);

  /* Check if this is a function like macro */
  if (CurC == '(') {

    /* Skip the left paren */
    NextChar();

    /* Set the marker that this is a function like macro */
    M->ArgCount = 0;

    /* Read the formal parameter list */
    while (1) {

      /* Skip white space and check for end of parameter list */
      SkipWhitespace(0);
      if (CurC == ')') {
        break;
      }

      /* The next token must be either an identifier, or - if not in
      ** C89 mode - the ellipsis.
      */
      if (!C89 && CurC == '.') {
        /* Ellipsis */
        NextChar();
        if (CurC != '.' || NextC != '.') {
          PPError("'...' expected");
          ClearLine();
          return;
        }
        NextChar();
        NextChar();

        /* Remember that the macro is variadic and use __VA_ARGS__ as
        ** the argument name.
        */
        AddMacroArg(M, "__VA_ARGS__");
        M->Variadic = 1;

      } else {
        /* Must be macro argument name */
        if (MacName(Ident) == 0) {
          return;
        }

        /* __VA_ARGS__ is only allowed in C89 mode */
        if (!C89 && strcmp(Ident, "__VA_ARGS__") == 0) {
          PPWarning("'__VA_ARGS__' can only appear in the expansion "
                    "of a C99 variadic macro");
        }

        /* Add the macro argument */
        AddMacroArg(M, Ident);
      }

      /* If we had an ellipsis, or the next char is not a comma, we've
      ** reached the end of the macro argument list.
      */
      SkipWhitespace(0);
      if (M->Variadic || CurC != ',') {
        break;
      }
      NextChar();
    }

    /* Check for a right paren and eat it if we find one */
    if (CurC != ')') {
      PPError("')' expected");
      ClearLine();
      return;
    }
    NextChar();
  }

  /* Skip whitespace before the macro replacement */
  SkipWhitespace(0);

  /* Insert the macro into the macro table and allocate the ActualArgs array */
  InsertMacro(M);

  /* Remove whitespace and comments from the line, store the preprocessed
  ** line into the macro replacement buffer.
  */
  Pass1(Line, &M->Replacement);

  /* Remove whitespace from the end of the line */
  while (IsSpace(SB_LookAtLast(&M->Replacement))) {
    SB_Drop(&M->Replacement, 1);
  }
#if 0
    printf ("%s: <%.*s>\n", M->Name, SB_GetLen (&M->Replacement), SB_GetConstBuf (&M->Replacement));
#endif

  /* If we have an existing macro, check if the redefinition is identical.
  ** Print a diagnostic if not.
  */
  if (Existing && MacroCmp(M, Existing) != 0) {
    PPError("Macro redefinition is not identical");
  }
}

/*****************************************************************************/
/*                               Preprocessing                               */
/*****************************************************************************/

static unsigned Pass1(StrBuf *Source, StrBuf *Target)
/* Preprocessor pass 1. Remove whitespace. Handle old and new style comments
** and the "defined" operator.
*/
{
  unsigned IdentCount;
  ident Ident;
  int HaveParen;

  /* Switch to the new input source */
  StrBuf *OldSource = InitLine(Source);

  /* Loop removing ws and comments */
  IdentCount = 0;
  while (CurC != '\0') {
    if (SkipWhitespace(0)) {
      /* Squeeze runs of blanks */
      if (!IsSpace(SB_LookAtLast(Target))) {
        SB_AppendChar(Target, ' ');
      }
    } else if (IsSym(Ident)) {
      if (Preprocessing && strcmp(Ident, "defined") == 0) {
        /* Handle the "defined" operator */
        SkipWhitespace(0);
        HaveParen = 0;
        if (CurC == '(') {
          HaveParen = 1;
          NextChar();
          SkipWhitespace(0);
        }
        if (IsSym(Ident)) {
          SB_AppendChar(Target, IsMacro(Ident) ? '1' : '0');
          if (HaveParen) {
            SkipWhitespace(0);
            if (CurC != ')') {
              PPError("')' expected");
            } else {
              NextChar();
            }
          }
        } else {
          PPError("Identifier expected");
          SB_AppendChar(Target, '0');
        }
      } else {
        ++IdentCount;
        SB_AppendStr(Target, Ident);
      }
    } else if (IsQuote(CurC)) {
      CopyQuotedString(Target);
    } else if (CurC == '/' && NextC == '*') {
      if (!IsSpace(SB_LookAtLast(Target))) {
        SB_AppendChar(Target, ' ');
      }
      OldStyleComment();
    } else if (IS_Get(&Standard) >= STD_C99 && CurC == '/' && NextC == '/') {
      if (!IsSpace(SB_LookAtLast(Target))) {
        SB_AppendChar(Target, ' ');
      }
      NewStyleComment();
    } else {
      SB_AppendChar(Target, CurC);
      NextChar();
    }
  }

  /* Switch back to the old source */
  InitLine(OldSource);

  /* Return the number of identifiers found in the line */
  return IdentCount;
}

static void MacroReplacement(StrBuf *Source, StrBuf *Target)
/* Perform macro replacement. */
{
  ident Ident;
  Macro *M;

  /* Remember the current input and switch to Source */
  StrBuf *OldSource = InitLine(Source);

  /* Loop substituting macros */
  while (CurC != '\0') {
    /* If we have an identifier, check if it's a macro */
    if (IsSym(Ident)) {
      /* Check if it's a macro */
      if ((M = FindMacro(Ident)) != 0 && !M->Expanding) {
        /* It's a macro, expand it */
        ExpandMacro(Target, M);
      } else {
        /* An identifier, keep it */
        SB_AppendStr(Target, Ident);
      }
    } else if (IsQuote(CurC)) {
      CopyQuotedString(Target);
    } else if (IsSpace(CurC)) {
      if (!IsSpace(SB_LookAtLast(Target))) {
        SB_AppendChar(Target, CurC);
      }
      NextChar();
    } else {
      SB_AppendChar(Target, CurC);
      NextChar();
    }
  }

  /* Switch back the input */
  InitLine(OldSource);
}

static void PreprocessLine(void)
/* Translate one line. */
{
  /* Trim whitespace and remove comments. The function returns the number of
  ** identifiers found. If there were any, we will have to check for macros.
  */
  SB_Clear(MLine);
  if (Pass1(Line, MLine) > 0) {
    MLine = InitLine(MLine);
    SB_Reset(Line);
    SB_Clear(MLine);
    MacroReplacement(Line, MLine);
  }

  /* Read from the new line */
  SB_Reset(MLine);
  MLine = InitLine(MLine);
}

static int PushIf(int Skip, int Invert, int Cond)
/* Push a new if level onto the if stack */
{
  /* Check for an overflow of the if stack */
  if (IfIndex >= MAX_IFS - 1) {
    PPError("Too many nested #if clauses");
    return 1;
  }

  /* Push the #if condition */
  ++IfIndex;
  if (Skip) {
    IfStack[IfIndex] = IFCOND_SKIP | IFCOND_NEEDTERM;
    return 1;
  } else {
    IfStack[IfIndex] = IFCOND_NONE | IFCOND_NEEDTERM;
    return (Invert ^ Cond);
  }
}

static void DoError(void)
/* Print an error */
{
  SkipWhitespace(0);
  if (CurC == '\0') {
    PPError("Invalid #error directive");
  } else {
    PPError("#error: %s", SB_GetConstBuf(Line) + SB_GetIndex(Line));
  }

  /* Clear the rest of line */
  ClearLine();
}

static int DoIf(int Skip)
/* Process #if directive */
{
  ExprDesc Expr;

  /* We're about to abuse the compiler expression parser to evaluate the
  ** #if expression. Save the current tokens to come back here later.
  ** NOTE: Yes, this is a hack, but it saves a complete separate expression
  ** evaluation for the preprocessor.
  */
  Token SavedCurTok = CurTok;
  Token SavedNextTok = NextTok;

  /* Make sure the line infos for the tokens won't get removed */
  if (SavedCurTok.LI) {
    UseLineInfo(SavedCurTok.LI);
  }
  if (SavedNextTok.LI) {
    UseLineInfo(SavedNextTok.LI);
  }

  /* Switch into special preprocessing mode */
  Preprocessing = 1;

  /* Expand macros in this line */
  PreprocessLine();

  /* Add two semicolons as sentinels to the line, so the following
  ** expression evaluation will eat these two tokens but nothing from
  ** the following line.
  */
  SB_AppendStr(Line, ";;");
  SB_Terminate(Line);

  /* Load CurTok and NextTok with tokens from the new input */
  NextToken();
  NextToken();

  /* Call the expression parser */
  ConstExpr(hie1, &Expr);

  /* End preprocessing mode */
  Preprocessing = 0;

  /* Reset the old tokens */
  CurTok = SavedCurTok;
  NextTok = SavedNextTok;

  /* Set the #if condition according to the expression result */
  return PushIf(Skip, 1, Expr.IVal != 0);
}

static int DoIfDef(int skip, int flag)
/* Process #ifdef if flag == 1, or #ifndef if flag == 0. */
{
  ident Ident;

  SkipWhitespace(0);
  if (MacName(Ident) == 0) {
    return 0;
  } else {
    return PushIf(skip, flag, IsMacro(Ident));
  }
}

static void DoInclude(void)
/* Open an include file. */
{
  char RTerm;
  InputType IT;
  StrBuf Filename = STATIC_STRBUF_INITIALIZER;

  /* Preprocess the remainder of the line */
  PreprocessLine();

  /* Skip blanks */
  SkipWhitespace(0);

  /* Get the next char and check for a valid file name terminator. Setup
  ** the include directory spec (SYS/USR) by looking at the terminator.
  */
  switch (CurC) {

  case '\"':
    RTerm = '\"';
    IT = IT_USRINC;
    break;

  case '<':
    RTerm = '>';
    IT = IT_SYSINC;
    break;

  default:
    PPError("'\"' or '<' expected");
    goto Done;
  }
  NextChar();

  /* Get a copy of the filename */
  while (CurC != '\0' && CurC != RTerm) {
    SB_AppendChar(&Filename, CurC);
    NextChar();
  }
  SB_Terminate(&Filename);

  /* Check if we got a terminator */
  if (CurC == RTerm) {
    /* Open the include file */
    OpenIncludeFile(SB_GetConstBuf(&Filename), IT);
  } else if (CurC == '\0') {
    /* No terminator found */
    PPError("#include expects \"FILENAME\" or <FILENAME>");
  }

Done:
  /* Free the allocated filename data */
  SB_Done(&Filename);

  /* Clear the remaining line so the next input will come from the new
  ** file (if open)
  */
  ClearLine();
}

static void DoPragma(void)
/* Handle a #pragma line by converting the #pragma preprocessor directive into
** the _Pragma() compiler operator.
*/
{
  /* Skip blanks following the #pragma directive */
  SkipWhitespace(0);

  /* Copy the remainder of the line into MLine removing comments and ws */
  SB_Clear(MLine);
  Pass1(Line, MLine);

  /* Convert the directive into the operator */
  SB_CopyStr(Line, "_Pragma (");
  SB_Reset(MLine);
  Stringize(MLine, Line);
  SB_AppendChar(Line, ')');

  /* Initialize reading from line */
  SB_Reset(Line);
  InitLine(Line);
}

static void DoUndef(void)
/* Process the #undef directive */
{
  ident Ident;

  SkipWhitespace(0);
  if (MacName(Ident)) {
    UndefineMacro(Ident);
  }
}

static void DoWarning(void)
/* Print a warning */
{
  SkipWhitespace(0);
  if (CurC == '\0') {
    PPError("Invalid #warning directive");
  } else {
    PPWarning("#warning: %s", SB_GetConstBuf(Line) + SB_GetIndex(Line));
  }

  /* Clear the rest of line */
  ClearLine();
}

void Preprocess(void)
/* Preprocess a line */
{
  int Skip;
  ident Directive;

  /* Create the output buffer if we don't already have one */
  if (MLine == 0) {
    MLine = NewStrBuf();
  }

  /* Skip white space at the beginning of the line */
  SkipWhitespace(0);

  /* Check for stuff to skip */
  Skip = 0;
  while (CurC == '\0' || CurC == '#' || Skip) {

    /* Check for preprocessor lines lines */
    if (CurC == '#') {
      NextChar();
      SkipWhitespace(0);
      if (CurC == '\0') {
        /* Ignore the empty preprocessor directive */
        continue;
      }
      if (!IsSym(Directive)) {
        PPError("Preprocessor directive expected");
        ClearLine();
      } else {
        switch (FindPPToken(Directive)) {

        case PP_DEFINE:
          if (!Skip) {
            DefineMacro();
          }
          break;

        case PP_ELIF:
          if (IfIndex >= 0) {
            if ((IfStack[IfIndex] & IFCOND_ELSE) == 0) {

              /* Handle as #else/#if combination */
              if ((IfStack[IfIndex] & IFCOND_SKIP) == 0) {
                Skip = !Skip;
              }
              IfStack[IfIndex] |= IFCOND_ELSE;
              Skip = DoIf(Skip);

              /* #elif doesn't need a terminator */
              IfStack[IfIndex] &= ~IFCOND_NEEDTERM;
            } else {
              PPError("Duplicate #else/#elif");
            }
          } else {
            PPError("Unexpected #elif");
          }
          break;

        case PP_ELSE:
          if (IfIndex >= 0) {
            if ((IfStack[IfIndex] & IFCOND_ELSE) == 0) {
              if ((IfStack[IfIndex] & IFCOND_SKIP) == 0) {
                Skip = !Skip;
              }
              IfStack[IfIndex] |= IFCOND_ELSE;
            } else {
              PPError("Duplicate #else");
            }
          } else {
            PPError("Unexpected '#else'");
          }
          break;

        case PP_ENDIF:
          if (IfIndex >= 0) {
            /* Remove any clauses on top of stack that do not
            ** need a terminating #endif.
            */
            while (IfIndex >= 0 && (IfStack[IfIndex] & IFCOND_NEEDTERM) == 0) {
              --IfIndex;
            }

            /* Stack may not be empty here or something is wrong */
            CHECK(IfIndex >= 0);

            /* Remove the clause that needs a terminator */
            Skip = (IfStack[IfIndex--] & IFCOND_SKIP) != 0;
          } else {
            PPError("Unexpected '#endif'");
          }
          break;

        case PP_ERROR:
          if (!Skip) {
            DoError();
          }
          break;

        case PP_IF:
          Skip = DoIf(Skip);
          break;

        case PP_IFDEF:
          Skip = DoIfDef(Skip, 1);
          break;

        case PP_IFNDEF:
          Skip = DoIfDef(Skip, 0);
          break;

        case PP_INCLUDE:
          if (!Skip) {
            DoInclude();
          }
          break;

        case PP_LINE:
          /* Should do something in C99 at least, but we ignore it */
          if (!Skip) {
            ClearLine();
          }
          break;

        case PP_PRAGMA:
          if (!Skip) {
            DoPragma();
            goto Done;
          }
          break;

        case PP_UNDEF:
          if (!Skip) {
            DoUndef();
          }
          break;

        case PP_WARNING:
          /* #warning is a non standard extension */
          if (IS_Get(&Standard) > STD_C99) {
            if (!Skip) {
              DoWarning();
            }
          } else {
            if (!Skip) {
              PPError("Preprocessor directive expected");
            }
            ClearLine();
          }
          break;

        default:
          if (!Skip) {
            PPError("Preprocessor directive expected");
          }
          ClearLine();
        }
      }
    }
    if (NextLine() == 0) {
      if (IfIndex >= 0) {
        PPError("'#endif' expected");
      }
      return;
    }
    SkipWhitespace(0);
  }

  PreprocessLine();

Done:
  if (Verbosity > 1 && SB_NotEmpty(Line)) {
    printf("%s(%u): %.*s\n", GetCurrentFile(), GetCurrentLine(), (int)SB_GetLen(Line), SB_GetConstBuf(Line));
  }
}
